package com.inspur.cloud.service.dataspace;

import com.inspur.cloud.configuration.AmbariConfig;
import com.inspur.cloud.configuration.ApiConfig;
import com.inspur.cloud.exception.ApiException;
import com.inspur.cloud.util.SshConf;
import com.inspur.cloud.util.SshUtil;
import com.inspur.cloud.util.TConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;

@Service
public class KerberosService {

    private static final Logger logger = LoggerFactory.getLogger(KerberosService.class);

    @Autowired
    private ApiConfig apiConfig;
    @Autowired
    private AmbariConfig ambariConfig;
    @Autowired
    private ClusterConfigUtil clusterConfigUtil;


    /**
     * 获取realm
     *
     * @return
     */
    public String getKerberosRealm() {
        Map<String, String> krb5ConfigInfo = clusterConfigUtil.getKerberosConfig();
        String kdcRealm = krb5ConfigInfo.get("realm");
        return kdcRealm;
    }

    /**
     * 获取kdc主机名
     *
     * @return
     */
    public String getKerberosKdcHostName() {
        Map<String, String> krb5ConfigInfo = clusterConfigUtil.getKerberosConfig();
        String kdcHostName = krb5ConfigInfo.get("kdc_hosts");
        if (kdcHostName.split(",").length > 1) {
            kdcHostName = kdcHostName.split(",")[0];
        }
        return kdcHostName;
    }

    /**
     * 新增kerberos票据信息
     * 使用所传密码生成keytab
     * @param userName
     * @param password
     * @return
     */
    public boolean addPrincipal(String userName, String password) {
        String kdcRealm = getKerberosRealm();
        String kdcHostName = getKerberosKdcHostName();
        logger.info("===============principalName================" + userName);
        userName = filterUserName(userName);
        //值如test@BIGDATA
        String entirePrincipalName = userName + "@" + kdcRealm;
        //值如/insight/keytabs/test.keytab
        String keytabsFullPath = getKeytabPathByUserName(userName);
        //值如/insight/keytabs/test.tar.gz
        String zipFullPath = getZipPathByUserName(userName);

        String createPrincCmd = String.format(
                "kadmin.local -q \"addprinc -pw %s %s\" -p admin/admin@%s -k -t %s/krb5.keytab",
                password, entirePrincipalName, kdcRealm, TConstants.KERBEROS_KRB5_PATH);
        String createKeytabCmd = String.format(
                "kadmin.local -q \"xst -norandkey -k %s %s\" -p admin/admin@%s -k -t %s/krb5.keytab",
                keytabsFullPath, entirePrincipalName, kdcRealm, TConstants.KERBEROS_KRB5_PATH);
        String tarKerberosCmd = String.format(
                "tar -czvf %s %s/krb5.conf %s",
                zipFullPath, TConstants.KERBEROS_KRB5_PATH, keytabsFullPath);
        try {
            SshUtil sshUtil = remoteIp(kdcHostName);
            logger.info("KerberosService addPrincipal begin to create principal");
            sshUtil.runCmd(createPrincCmd);
            logger.info("KerberosService addPrincipal begin to create keytab:" + createKeytabCmd);
            sshUtil.runCmd(createKeytabCmd);
            logger.info("KerberosService addPrincipal begin to tar zip:" + tarKerberosCmd);
            sshUtil.runCmd(tarKerberosCmd);
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new ApiException("KerberosService addPrincipal name[" + userName + "] error.");
        }
    }

    /**
     * 新增Dataspace的kerberos票据信息
     * 初始化时可以调用此方法
     * @param userName
     * @return
     */
    public boolean addDataspacePrincipal(String userName) {
        String kdcRealm = getKerberosRealm();
        String kdcHostName = getKerberosKdcHostName();
        logger.info("===============principalName================" + userName);
        userName = filterUserName(userName);
        //值如test@BIGDATA
        String entirePrincipalName = userName + "@" + kdcRealm;
        String keytabsFullPath = TConstants.KEYTAB_PATH + TConstants.DATASPACE_USER + ".keytab";


        String createPrincCmd = String.format(
                "kadmin.local -q \"addprinc -randkey %s\" -p admin/admin@%s -k -t %s/krb5.keytab",
                entirePrincipalName, kdcRealm, TConstants.KERBEROS_KRB5_PATH);
        String createKeytabCmd = String.format(
                "kadmin.local -q \"xst -k %s %s\" -p admin/admin@%s -k -t %s/krb5.keytab",
                keytabsFullPath, entirePrincipalName, kdcRealm, TConstants.KERBEROS_KRB5_PATH);
        try {
            SshUtil sshUtil = remoteIp(kdcHostName);
            logger.info("KerberosService addPrincipal begin to create principal");
            sshUtil.runCmd(createPrincCmd);
            logger.info("KerberosService addPrincipal begin to create keytab:" + createKeytabCmd);
            sshUtil.runCmd(createKeytabCmd);
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new ApiException("KerberosService addDataspacePrincipal name[" + userName + "] error.");
        }
    }

    /**
     * 根据票据名称删除kerberos票据和keytab文件
     * userName可以是abc也可以是abc@realm
     * @param userName
     * @return
     */
    public void deletePrincipal(String userName) {
        String kdcRealm = getKerberosRealm();
        String kdcHostName = getKerberosKdcHostName();
        logger.info("===============principalName================" + userName);
        userName = filterUserName(userName);
        //值如test@BIGDATA
        String entirePrincipalName = userName + "@" + kdcRealm;
        //值如/insight/keytabs/test.keytab
        String keytabsFullPath = getKeytabPathByUserName(userName);
        //值如/insight/keytabs/test.tar.gz
        String zipFullPath = getZipPathByUserName(userName);

        // 在kerberos服务器上执行删除principal的sh命令
        String deletePrincCmd = String.format(
                "kadmin.local -q \"delprinc -force %s\" -p admin/admin@%s -k -t %s/krb5.keytab",
                entirePrincipalName, kdcRealm, TConstants.KERBEROS_KRB5_PATH);
        String deleteKeytabCmd = String.format("rm -rf %s", keytabsFullPath);
        String deleteTarCmd = String.format("rm -rf %s", zipFullPath);

        try {
            SshUtil sshUtil = remoteIp(kdcHostName);
            logger.info("KerberosService addPrincipal begin to delete principal:" + deletePrincCmd);
            sshUtil.runCmd(deletePrincCmd);
            logger.info("KerberosService addPrincipal begin to delete keytab:" + deleteKeytabCmd);
            sshUtil.runCmd(deleteKeytabCmd);
            logger.info("KerberosService addPrincipal begin to delete tar:" + deleteTarCmd);
            sshUtil.runCmd(deleteTarCmd);
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new ApiException("KerberosService delPrinc princName[" + userName + "] error.");
        }
    }


    /**
     * 修改票据的密码，不支持特殊字符
     * @param userName
     * @param newPassword
     * @return
     */
    public boolean changePrincipalPassword(String userName, String newPassword) {
        logger.info("===============principalName================" + userName);
        boolean result = false;
        logger.info("--PrincipalRepository changePassword start--");
        try {
            deletePrincipal(userName);
            addPrincipal(userName, newPassword);
            result = true;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new ApiException("KerberosService changePwdByDelete error.");
        }
        logger.info("--PrincipalRepository changePassword end--");
        return result;
    }

    /**
     * 判断kerberos票据是否已经存在
     *
     * @param userName
     * @return
     */
    public boolean findPrincipalExists(String userName) {
        try {
            String kdcRealm = getKerberosRealm();
            String kdcHostName = getKerberosKdcHostName();
            logger.info("--PrincipalRepository findPrincipalExists start--");
            userName = filterUserName(userName);
            String entirePrincipalName = userName + "@" + kdcRealm;
            SshUtil sshUtil = remoteIp(kdcHostName);

            // 在kerberos服务器上执行删除principal的sh命令
            String findPrincExists = "kadmin.local -q \"getprinc "
                    + entirePrincipalName + "\"";


            logger.info("--findPrincipalExists:" + findPrincExists);

            //从返回流中判断是否用户存在
            String result = sshUtil.runCmdWithReturnInfo(findPrincExists, "UTF-8");
            if (!result.equals("") && result.contains("does not exist while")) {
                return false;
            }
            logger.info("--PrincipalRepository findPrincipalExists end--");
            return true;
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new ApiException("KerberosService findPrincipalExists name[" + userName + "] error.");
        }

    }

    /**
     * 读取krb5.conf文件
     * 得到server的主机名和realm值
     *
     * @return
     */
    private Map<String, String> getKrb5ConfigInfo() {
        Map<String, String> krb5ConfigMap = new HashMap<>();
        logger.info("begin to get krb5.conf information.");
        String krb5File = TConstants.KERBEROS_KRB5_PATH + "/krb5.conf";
        String os = System.getProperty("os.name");
        if (os.toLowerCase(Locale.CHINA).startsWith("win")) {
            krb5File = Thread.currentThread().getContextClassLoader().getResource("tempfiles/krb5.conf").toString();
            krb5File = krb5File.substring(5, krb5File.length());
        }
        logger.info("KerberosService getRealm get krb5 path :" + krb5File);
        String kdcServerHostName = "";
        String kdcServerRealm = "";
        //临时集合，从krb5.conf中读出的admin_server不止一条
        List<String> kadminServerReadList = new ArrayList<>();
        try(BufferedReader br = new BufferedReader(new FileReader(krb5File))) {
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                if (line.contains("default_realm")) {
                    kdcServerRealm = line.split("=")[1].trim();
                }
                if (line.contains("admin_server")) {
                    kadminServerReadList.add(line.split("=")[1].trim());
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new ApiException("KerberosService getKrb5ConfigInfo read from krb5File error.");
        }
        //筛选出正确的hostname
        kdcServerHostName = pickKadminHostName(kadminServerReadList);
        if (kdcServerRealm.equals("") || kdcServerHostName.equals("")) {
            throw new ApiException("KerberosService getKrb5ConfigInfo host[" + kdcServerHostName + "] realm[" + kdcServerRealm + "] error.");
        }
        krb5ConfigMap.put("realm", kdcServerRealm);
        krb5ConfigMap.put("hostname", kdcServerHostName);
        return krb5ConfigMap;
    }

    /**
     * 筛选出kadmin的主机名
     *
     * @param kadminServerReadList
     * @return
     */
    private String pickKadminHostName(List<String> kadminServerReadList) {
        String hostName = "";
        if (kadminServerReadList == null || kadminServerReadList.size() == 0) {
            throw new ApiException("KerberosService pickKadminHostName list is null");
        }
        for (String tmpName : kadminServerReadList) {
            if (tmpName.contains("FILE") || tmpName.contains(".log")) continue;
            hostName = tmpName;
            break;
        }
        return hostName;
    }

    /**
     * 过滤输入的用户名，如果包含@realm，则过滤
     * 得到的结果是@之前的标准用户名
     *
     * @param userName
     * @return
     */
    private String filterUserName(String userName) {
        String kdcRealm = getKerberosRealm();
        String realm = "@" + kdcRealm;
        if (userName.indexOf(realm) > 0) {
            userName = userName.substring(0, userName.indexOf(realm));
        }
        return userName;
    }

    /**
     * 根据传过来的用户名拼接成kerberos真正的完整票据名
     *
     * @param name
     * @return
     */
    public String getPrincipalNameByUserName(String name) {
        String kdcRealm = getKerberosRealm();
        name = filterUserName(name);
        String principalName = name + "@" + kdcRealm;
        return principalName;
    }

    /**
     * 根据用户名拼接完整的keytab路径名
     * 如用户是test，则返回结果是/etc/security/keytabs/test.keytab
     *
     * @param name
     * @return
     */
    public String getKeytabPathByUserName(String name) {
        String keytabName = getKeytabNameByUserName(name);
        String keytabsPath = ambariConfig.getUserKeytabPath();
        String path = keytabsPath + "/" + keytabName;
        return path;
    }

    /**
     * 根据用户名拼接对应keytab的文件名，返回结果如test.keytab
     *
     * @param name
     * @return
     */
    public String getKeytabNameByUserName(String name) {
        name = filterUserName(name);
        String keytabName = name + ".keytab";
        return keytabName;
    }

    /**
     * 根据用户名拼接对应tar.gz的文件名，返回结果如test.tar.gz
     *
     * @param name
     * @return
     */
    public String getZipNameByUserName(String name) {
        name = filterUserName(name);
        String zipName = name + ".tar.gz";
        return zipName;
    }

    /**
     * 根据用户名拼接完整的Zip路径名
     * 如用户是test，则返回结果是/etc/security/keytabs/test.tar.gz
     *
     * @param name
     * @return
     */
    public String getZipPathByUserName(String name) {
        String zipName = getZipNameByUserName(name);
        String keytabsPath = ambariConfig.getUserKeytabPath();
        String path = keytabsPath + "/" + zipName;
        return path;
    }

    /**
     * 返回/etc/security/keytabs,末尾不带/
     *
     * @return
     */
    public String getKeytabAndZipPath() {
        return ambariConfig.getUserKeytabPath();
    }

    /**
     * SSH连接远程机器
     *
     * @param hostName
     * @return
     * @throws Exception
     */
    public SshUtil remoteIp(String hostName) throws Exception {
        // ssh远程连接kerberos服务器
        SshConf conf = new SshConf();
        conf.setHost(hostName);
        logger.info("--Kerberos RemoteIp SERVER_IP--:" + hostName);
        conf.setPort(Integer.parseInt(ambariConfig.getSshPort()));
        conf.setUsername(ambariConfig.getAmbariServerLoginUser());
//        conf.setPassword(ambariConfig.getAmbariServerLoginPass());
        SshUtil sshUtil = new SshUtil(conf); // 连接服务器
        return sshUtil;
    }

}
